package org.zsk.cs.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.*;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.zsk.cs.response.ResultBody;
import org.zsk.cs.service.UserService;
import org.zsk.cs.util.UserUtils;

import java.io.PrintWriter;

/**
 * Web安全配置类
 * @author zsk
 * @version 1.0
 * Create on 2019/11/14
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Slf4j
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private final UserService userService;
    private final CustomFilterInvocationSecurityMetadataSource metadataSource;
    private final CustomAccessDecisionManager customAccessDecisionManager;
    private final AuthenticationAccessDeniedHandler deniedHandler;
    private final PasswordEncoder passwordEncoder;
    private final PersistentTokenRepository persistentTokenRepository;

    public WebSecurityConfig(UserService userService, CustomFilterInvocationSecurityMetadataSource metadataSource, CustomAccessDecisionManager customAccessDecisionManager, AuthenticationAccessDeniedHandler deniedHandler, PasswordEncoder passwordEncoder, PersistentTokenRepository persistentTokenRepository) {
        this.userService = userService;
        this.metadataSource = metadataSource;
        this.customAccessDecisionManager = customAccessDecisionManager;
        this.deniedHandler = deniedHandler;
        this.passwordEncoder = passwordEncoder;
        this.persistentTokenRepository = persistentTokenRepository;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService)
            .passwordEncoder(passwordEncoder);
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/index.html","/login_p","/emailactive.html","/res/**",
                "/mail/emailvcodeForResetPass","/user/resetpass","/user/register",
                "/v2/api-docs", "/swagger-resources/configuration/ui",
                "/swagger-resources", "/swagger-resources/configuration/security",
                "/swagger-ui.html", "/webjars/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors();
        http.authorizeRequests()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        object.setSecurityMetadataSource(metadataSource);
                        object.setAccessDecisionManager(customAccessDecisionManager);
                        return object;
                    }
                })
                .and()
                .formLogin().loginPage("/login_p").loginProcessingUrl("/login")
                .usernameParameter("username").passwordParameter("password")
                .failureHandler((req, resp, e) -> {
                    resp.setContentType("application/json;charset=utf-8");
                    ResultBody resultBody=null;
                    if (e instanceof BadCredentialsException ||
                            e instanceof UsernameNotFoundException) {
                        resultBody = ResultBody.error("账户名或者密码输入错误!");
                    } else if (e instanceof LockedException) {
                        resultBody = ResultBody.error("账户被锁定，请联系管理员!");
                    } else if (e instanceof CredentialsExpiredException) {
                        resultBody = ResultBody.error("密码过期，请联系管理员!");
                    } else if (e instanceof AccountExpiredException) {
                        resultBody = ResultBody.error("账户过期，请联系管理员!");
                    } else if (e instanceof DisabledException) {
                        resultBody = ResultBody.error("账户被禁用，请联系管理员!");
                    } else {
                        resultBody = ResultBody.error("登录失败!");
                    }
                    resp.setStatus(401);
                    ObjectMapper om = new ObjectMapper();
                    PrintWriter out = resp.getWriter();
                    out.write(om.writeValueAsString(resultBody));
                    out.flush();
                    out.close();
                })
                .successHandler((req, resp, auth) -> {
                    resp.setContentType("application/json;charset=utf-8");
                    ResultBody resultBody = ResultBody.success(UserUtils.getCurrentUser());
                    ObjectMapper om = new ObjectMapper();
                    PrintWriter out = resp.getWriter();
                    out.write(om.writeValueAsString(resultBody));
                    out.flush();
                    out.close();
                })
                .permitAll()
                .and()
                .logout()
                .logoutUrl("/logout")
                .deleteCookies("JSESSIONID")
                .logoutSuccessHandler((req, resp, authentication) -> {
                    log.debug("注销");
                    resp.setContentType("application/json;charset=utf-8");
                    ResultBody resultBody = ResultBody.success();
                    ObjectMapper om = new ObjectMapper();
                    PrintWriter out = resp.getWriter();
                    out.write(om.writeValueAsString(resultBody));
                    out.flush();
                    out.close();
                })
                .permitAll()
                .and()
                .rememberMe()
                    .key("rem-me-key")
                    .tokenRepository(persistentTokenRepository)
                    .rememberMeParameter("rememberMe")
                    .rememberMeCookieName("rememberMe")
                    .tokenValiditySeconds(2 * 24 * 60 * 60)
                .and().csrf().disable()
                .exceptionHandling().accessDeniedHandler(deniedHandler);
    }
}
